/*
 * $Id: JXGraph.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;

import org.jdesktop.beans.AbstractBean;
import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.painter.Painter;

// TODO: keyboard navigation
// TODO: honor clip rect with text painting
// TODO: let client change zoom multiplier
// TODO: improve text drawing when origin is not on a multiple of majorX/majorY
// TODO: programmatically zoom in and out (or expose ZOOM_MULTIPLIER)

/**
 * <p>
 * <code>JXGraph</code> provides a component which can display one or more plots
 * on top of a graduated background (or grid.)
 * </p>
 * 
 * <h2>User input</h2>
 * 
 * <p>
 * To help analyze the plots, this component allows the user to pan the view by
 * left-clicking and dragging the mouse around. Using the mouse wheel, the user
 * is also able to zoom in and out. Clicking the middle button resets the view
 * to its original position.
 * </p>
 *
 * <p>
 * All user input can be disabled by calling {@link #setInputEnabled(boolean)}
 * and passing false. This does not prevent subclasses from registering their
 * own event listeners, such as mouse or key listeners.
 * </p>
 *
 * <h2>Initializing the component and setting the view</h2>
 *
 * <p>
 * Whenever a new instance of this component is created, the grid boundaries, or
 * view, must be defined. The view is comprised of several elements whose
 * descriptions are the following:
 * </p>
 *
 * <ul>
 * <li><i>minX</i>: Minimum value initially displayed by the component on the X
 * axis (horizontally.)</li>
 * <li><i>minY</i>: Minimum value initially displayed by the component on the Y
 * axis (vertically.)</li>
 * <li><i>maxX</i>: Maximum value initially displayed by the component on the X
 * axis (horizontally.)</li>
 * <li><i>maxY</i>: Maximum value initially displayed by the component on the Y
 * axis (vertically.)</li>
 * <li><i>originX</i>: Origin on the X axis of the vertical axis.</li>
 * <li><i>originY</i>: Origin on the Y axis of the horizontal axis.</li>
 * <li><i>majorX</i>: Distance between two major vertical lines of the grid.</li>
 * <li><i>majorY</i>: Distance between two major horizontal lines of the grid.</li>
 * <li><i>minCountX</i>: Number of minor vertical lines between two major
 * vertical lines in the grid.</li>
 * <li><i>minCountY</i>: Number of minor horizontal lines between two major
 * horizontal lines in the grid.</li>
 * </ul>
 *
 * <h3>View and origin</h3>
 *
 * <p>
 * The default constructor defines a view bounds by <code>-1.0</code> and
 * <code>+1.0</code> on both axis, and centered on an origin at
 * <code>(0, 0)</code>.
 * </p>
 *
 * <p>
 * To simplify the API, the origin can be read and written with a
 * <code>Point2D</code> instance (see {@link #getOrigin()} and
 * {@link #setOrigin(Point2D)}.)
 * </p>
 *
 * <p>
 * Likewise, the view can be read and written with a <code>Rectangle2D</code>
 * instance (see {@link #getView()} and {@link #setView(Rectangle2D)}.) In this
 * case, you need not to define the maximum boundaries of the view. Instead, you
 * need to set the origin of the rectangle as the minimum boundaries. The width
 * and the height of the rectangle define the distance between the minimum and
 * maximum boundaries. For instance, to set the view to minX=-1.0, maxX=1.0,
 * minY=-1.0 and maxY=1.0 you can use the following rectangle:
 * </p>
 *
 * <pre>
 * new Rectangle2D.Double(-1.0d, -1.0d, 2.0d, 2.0d);
 * </pre>
 *
 * <p>
 * You can check the boundaries by calling <code>Rectangle2D.getMaxX()</code>
 * and <code>Rectangle2D.getMaxY()</code> once your rectangle has been created.
 * </p>
 *
 * <p>
 * Alternatively, you can set the view and the origin at the same time by
 * calling the method {@link #setViewAndOrigin(Rectangle2D)}. Calling this
 * method will set the origin so as to center it in the view defined by the
 * rectangle.
 * </p>
 *
 * <h3>Grid lines</h3>
 *
 * <p>
 * By default, the component defines a spacing of 0.2 units between two major
 * grid lines. It also defines 4 minor grid lines between two major grid lines.
 * The spacing between major grid lines and the number of minor grid lines can
 * be accessed through the getters {@link #getMajorX()}, {@link #getMajorY()},
 * {@link #getMinorCountX()} and {@link #getMinorCountY()}.
 * </p>
 *
 * <p>
 * You can change the number of grid lines at runtime by calling the setters
 * {@link #setMajorX(double)}, {@link #setMajorY(double)},
 * {@link #setMinorCountX(int)} and {@link #setMinorCountY(int)}.
 * </p>
 *
 * <h3>Appearance</h3>
 *
 * <p>
 * Although it provides sensible defaults, this component lets you change its
 * appearance in several ways. It is possible to modify the colors of the graph
 * by calling the setters {@link #setAxisColor(Color)},
 * {@link #setMajorGridColor(Color)} and {@link #setMinorGridColor(Color)}.
 * </p>
 *
 * <p>
 * You can also enable or disable given parts of the resulting graph by calling
 * the following setters:
 * </p>
 *
 * <ul>
 * <li>{@link #setAxisPainted(boolean)}: Defines whether the main axis (see
 * {@link #getOrigin()}) is painted.</li>
 * <li>{@link #setBackgroundPainted(boolean)}: Defines whether the background is
 * painted (see {@link #setBackground(Color)}.)</li>
 * <li>{@link #setGridPainted(boolean)}: Defines whether the grid is painted.</li>
 * <li>{@link #setTextPainted(boolean)}: Defines whether the axis labels are
 * painted.</li>
 * </ul>
 *
 * <h3>Usage example</h3>
 *
 * <p>
 * The following code snippet creates a new graph centered on
 * <code>(0, 0)</code>, bound to the view <code>[-1.0 1.0 -1.0 1.0]</code>, with
 * a major grid line every 0.5 units and a minor grid line count of 5:
 * </p>
 *
 * <pre>
 * Point2D origin = new Point2D.Double(0.0d, 0.0d);
 * Rectangle2D view = new Rectangle2D.Double(-1.0d, 1.0d, 2.0d, 2.0d);
 * JXGraph graph = new JXGraph(origin, view, 0.5d, 5, 0.5d, 5);
 * </pre>
 *
 * <h2>Plots</h2>
 *
 * <h3>Definition</h3>
 *
 * <p>
 * A plot is defined by a mathematical transformation that, given a value on the
 * graph's X axis, returns a value on the Y axis. The component draws the result
 * by plotting a spot of color at the coordinates defined by
 * <code>(X, f(X))</code> where <code>f()</code> is the aforementionned
 * mathematical transformation. Given the following transformation:
 * </p>
 *
 * <pre>
 * f(X) = X * 2.0
 * </pre>
 *
 * <p>
 * For <code>X=1.0</code>, the component will show a spot of color at the
 * coordinates <code>(1.0, 2.0)</code>.
 * </p>
 *
 * <h3>Creating a new plot</h3>
 *
 * <p>
 * Every plot drawn by the component must be a subclass of {@link JXGraph.Plot}.
 * This abstract public class defines a single method to be implemented by its
 * children:
 * </p>
 *
 * <pre>
 * public double compute(double value)
 * </pre>
 *
 * <p>
 * The previous example can be defined by a concrete <code>JXGraph.Plot</code>
 * as follow:
 * </p>
 *
 * <pre>
 * class TwiceTheValuePlot extends JXGraph.Plot {
 * 	public double compute(double value) {
 * 		return value * 2.0d;
 * 	}
 * }
 * </pre>
 *
 * <p>
 * Most of the time though, a plot requires supplementary parameters. For
 * instance, let's define the X axis of your graph as the mass of an object. To
 * compute the weight of the object given its mass, you need to use the
 * acceleration of gravity (<code>w=m*g</code> where <code>g</code> is the
 * acceleration.) To let the user modify this last parameter, to compute his
 * weight at the surface of the moon for instance, you need to add a parameter
 * to your plot.
 * </p>
 *
 * <p>
 * While <code>JXGraph.Plot</code> does not give you an API for such a purpose,
 * it does define an event dispatching API (see
 * {@link JXGraph#firePropertyChange(String, double, double)}.) Whenever a plot
 * is added to the graph, the component registers itself as a property listener
 * of the plot. If you take care of firing events whenever the user changes a
 * parameter of your plot, the graph will automatically update its display.
 * While not mandatory, it is highly recommended to leverage this API.
 * </p>
 *
 * <h3>Adding and removing plots to and from the graph</h3>
 *
 * <p>
 * To add a plot to the graph, simply call the method
 * {@link #addPlots(Color, JXGraph.Plot...)}. You can use it to add one or more
 * plots at the same time and associate them with a color. This color is used
 * when drawing the plots:
 * </p>
 *
 * <pre>
 * JXGraph.Plot plot = new TwiceTheValuePlot();
 * graph.addPlots(Color.BLUE, plot);
 * </pre>
 *
 * <p>
 * These two lines will display our previously defined plot in blue on screen.
 * Removing one or several plots is as simple as calling the method
 * {@link #removePlots(JXGraph.Plot...)}. You can also remove all plots at once
 * with {@link #removeAllPlots()}.
 * </p>
 *
 * <h2>Painting more information</h2>
 *
 * <h3>How to draw on the graph</h3>
 *
 * <p>
 * If you need to add more information on the graph you need to extend it and
 * override the method {@link #paintExtra(Graphics2D)}. This method has a
 * default empty implementation and is called after everything has been drawn.
 * Its sole parameter is a reference to the component's drawing surface, as
 * configured by {@link #setupGraphics(Graphics2D)}. By default, the setup
 * method activates antialising but it can be overriden to change the drawing
 * surface. (Translation, rotation, new rendering hints, etc.)
 * </p>
 *
 * <h3>Getting the right coordinates</h3>
 *
 * <p>
 * To properly draw on the graph you will need to perform a translation between
 * the graph's coordinates and the screen's coordinates. The component defines 4
 * methods to assist you in this task:
 * </p>
 *
 * <ul>
 * <li>{@link #xPixelToPosition(double)}: Converts a pixel coordinate on the X
 * axis into a world coordinate.</li>
 * <li>{@link #xPositionToPixel(double)}: Converts a world coordinate on the X
 * axis into a pixel coordinate.</li>
 * <li>{@link #yPixelToPosition(double)}: Converts a pixel coordinate on the Y
 * axis into a world coordinate.</li>
 * <li>{@link #yPositionToPixel(double)}: Converts a world coordinate on the Y
 * axis into a pixel coordinate.</li>
 * </ul>
 *
 * <p>
 * If you have defined a graph view centered on the origin <code>(0, 0)</code>,
 * the origin of the graph will be at the exact center of the screen. That means
 * the world coordinates <code>(0, 0)</code> are equivalent to the pixel
 * coordinates <code>(width / 2, height / 2)</code>. Thus, calling
 * <code>xPositionToPixel(0.0d)</code> would give you the same value as the
 * expression <code>getWidth() / 2.0d</code>.
 * </p>
 *
 * <p>
 * Converting from world coordinates to pixel coordinates is mostly used to draw
 * the result of a mathematical transformation. Converting from pixel
 * coordinates to world coordinates is mostly used to get the position in the
 * world of a mouse event.
 * </p>
 *
 * @see JXGraph.Plot
 * @author Romain Guy <romain.guy@mac.com>
 */
@JavaBean
public class JXGraph extends JXPanel {
	// stroke widths used to draw the main axis and the grid
	// the main axis is slightly thicker
	private static final float STROKE_AXIS = 1.2f;
	private static final float STROKE_GRID = 1.0f;

	// defines by how much the view is shrinked or expanded everytime the
	// user zooms in or out
	private static final float ZOOM_MULTIPLIER = 1.1f;

	// listens to changes to plots and repaints the graph
	private PropertyChangeListener plotChangeListener;

	// default color of the graph (does not include plots colors)
	private Color majorGridColor = Color.GRAY.brighter();
	private Color minorGridColor = new Color(220, 220, 220);
	private Color axisColor = Color.BLACK;

	// the list of plots currently known and displayed by the graph
	private List<DrawablePlot> plots;

	// view boundaries as defined by the user
	private double minX;
	private double maxX;
	private double minY;
	private double maxY;

	// the default view is set when the view is manually changed by the client
	// it is used to reset the view in resetView()
	private Rectangle2D defaultView;

	// coordinates of the major axis
	private double originX;
	private double originY;

	// definition of the grid
	// various default values are used when the view is reset
	private double majorX;
	private double defaultMajorX;
	private int minorCountX;
	private double majorY;
	private double defaultMajorY;
	private int minorCountY;

	// enables painting layers
	private boolean textPainted = true;
	private boolean gridPainted = true;
	private boolean axisPainted = true;
	private boolean backPainted = true;

	// used by the PanHandler to move the view
	private Point dragStart;

	// mainFormatter is used for numbers > 0.01 and < 100
	// secondFormatter uses scientific notation
	private NumberFormat mainFormatter;
	private NumberFormat secondFormatter;

	// input handlers
	private boolean inputEnabled = true;
	private ZoomHandler zoomHandler;
	private PanMotionHandler panMotionHandler;
	private PanHandler panHandler;
	private ResetHandler resetHandler;

	/**
	 * <p>
	 * Creates a new graph display. The following properties are automatically
	 * set:
	 * </p>
	 * <ul>
	 * <li><i>view</i>: -1.0 to +1.0 on both axis</li>
	 * <li><i>origin</i>: At <code>(0, 0)</code></li>
	 * <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines count is
	 * 4</li>
	 * </ul>
	 */
	public JXGraph() {
		this(0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 0.2, 4, 0.2, 4);
	}

	/**
	 * <p>
	 * Creates a new graph display with the specified view. The following
	 * properties are automatically set:
	 * </p>
	 * <ul>
	 * <li><i>origin</i>: Center of the specified view</code></li>
	 * <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines count is
	 * 4</li>
	 * </ul>
	 * 
	 * @param view
	 *            the rectangle defining the view boundaries
	 */
	public JXGraph(Rectangle2D view) {
		this(new Point2D.Double(view.getCenterX(), view.getCenterY()), view, 0.2, 4, 0.2, 4);
	}

	/**
	 * <p>
	 * Creates a new graph display with the specified view and grid lines. The
	 * origin is set at the center of the view.
	 * </p>
	 * 
	 * @param view
	 *            the rectangle defining the view boundaries
	 * @param majorX
	 *            the spacing between two major grid lines on the X axis
	 * @param minorCountX
	 *            the number of minor grid lines between two major grid lines on
	 *            the X axis
	 * @param majorY
	 *            the spacing between two major grid lines on the Y axis
	 * @param minorCountY
	 *            the number of minor grid lines between two major grid lines on
	 *            the Y axis
	 * @throws IllegalArgumentException
	 *             if minX >= maxX or minY >= maxY or minorCountX < 0 or
	 *             minorCountY < 0 or majorX <= 0.0 or majorY <= 0.0
	 */
	public JXGraph(Rectangle2D view, double majorX, int minorCountX, double majorY, int minorCountY) {
		this(new Point2D.Double(view.getCenterX(), view.getCenterY()), view, majorX, minorCountX, majorY, minorCountY);
	}

	/**
	 * <p>
	 * Creates a new graph display with the specified view and origin. The
	 * following properties are automatically set:
	 * </p>
	 * <ul>
	 * <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines count is
	 * 4</li>
	 * </ul>
	 * 
	 * @param origin
	 *            the coordinates of the main axis origin
	 * @param view
	 *            the rectangle defining the view boundaries
	 */
	public JXGraph(Point2D origin, Rectangle2D view) {
		this(origin, view, 0.2, 4, 0.2, 4);
	}

	/**
	 * <p>
	 * Creates a new graph display with the specified view, origin and grid
	 * lines.
	 * </p>
	 * 
	 * @param origin
	 *            the coordinates of the main axis origin
	 * @param view
	 *            the rectangle defining the view boundaries
	 * @param majorX
	 *            the spacing between two major grid lines on the X axis
	 * @param minorCountX
	 *            the number of minor grid lines between two major grid lines on
	 *            the X axis
	 * @param majorY
	 *            the spacing between two major grid lines on the Y axis
	 * @param minorCountY
	 *            the number of minor grid lines between two major grid lines on
	 *            the Y axis
	 * @throws IllegalArgumentException
	 *             if minX >= maxX or minY >= maxY or minorCountX < 0 or
	 *             minorCountY < 0 or majorX <= 0.0 or majorY <= 0.0
	 */
	public JXGraph(Point2D origin, Rectangle2D view, double majorX, int minorCountX, double majorY, int minorCountY) {
		this(origin.getX(), origin.getY(), view.getMinX(), view.getMaxX(), view.getMinY(), view.getMaxY(), majorX, minorCountX,
				majorY, minorCountY);
	}

	/**
	 * <p>
	 * Creates a new graph display with the specified view, origin and grid
	 * lines.
	 * </p>
	 * 
	 * @param originX
	 *            the coordinate of the major X axis
	 * @param originY
	 *            the coordinate of the major Y axis
	 * @param minX
	 *            the minimum coordinate on the X axis for the view
	 * @param maxX
	 *            the maximum coordinate on the X axis for the view
	 * @param minY
	 *            the minimum coordinate on the Y axis for the view
	 * @param maxY
	 *            the maximum coordinate on the Y axis for the view
	 * @param majorX
	 *            the spacing between two major grid lines on the X axis
	 * @param minorCountX
	 *            the number of minor grid lines between two major grid lines on
	 *            the X axis
	 * @param majorY
	 *            the spacing between two major grid lines on the Y axis
	 * @param minorCountY
	 *            the number of minor grid lines between two major grid lines on
	 *            the Y axis
	 * @throws IllegalArgumentException
	 *             if minX >= maxX or minY >= maxY or minorCountX < 0 or
	 *             minorCountY < 0 or majorX <= 0.0 or majorY <= 0.0
	 */
	public JXGraph(double originX, double originY, double minX, double maxX, double minY, double maxY, double majorX,
			int minorCountX, double majorY, int minorCountY) {
		if (minX >= maxX) {
			throw new IllegalArgumentException("minX must be < to maxX");
		}

		if (minY >= maxY) {
			throw new IllegalArgumentException("minY must be < to maxY");
		}

		if (minorCountX < 0) {
			throw new IllegalArgumentException("minorCountX must be >= 0");
		}

		if (minorCountY < 0) {
			throw new IllegalArgumentException("minorCountY must be >= 0");
		}

		if (majorX <= 0.0) {
			throw new IllegalArgumentException("majorX must be > 0.0");
		}

		if (majorY <= 0.0) {
			throw new IllegalArgumentException("majorY must be > 0.0");
		}

		this.originX = originX;
		this.originY = originY;

		this.minX = minX;
		this.maxX = maxX;
		this.minY = minY;
		this.maxY = maxY;

		this.defaultView = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);

		this.setMajorX(this.defaultMajorX = majorX);
		this.setMinorCountX(minorCountX);
		this.setMajorY(this.defaultMajorY = majorY);
		this.setMinorCountY(minorCountY);

		this.plots = new LinkedList<DrawablePlot>();

		this.mainFormatter = NumberFormat.getInstance();
		this.mainFormatter.setMaximumFractionDigits(2);

		this.secondFormatter = new DecimalFormat("0.##E0");

		resetHandler = new ResetHandler();
		addMouseListener(resetHandler);
		panHandler = new PanHandler();
		addMouseListener(panHandler);
		panMotionHandler = new PanMotionHandler();
		addMouseMotionListener(panMotionHandler);
		zoomHandler = new ZoomHandler();
		addMouseWheelListener(zoomHandler);

		setBackground(Color.WHITE);
		setForeground(Color.BLACK);

		plotChangeListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				repaint();
			}
		};
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isOpaque() {
		if (!isBackgroundPainted()) {
			return false;
		}
		return super.isOpaque();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see #setInputEnabled(boolean)
	 */
	@Override
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		setInputEnabled(enabled);
	}

	/**
	 * <p>
	 * Enables or disables user input on the component. When user input is
	 * enabled, panning, zooming and view resetting. Disabling input will
	 * prevent the user from modifying the currently displayed view.
	 * <p>
	 * <p>
	 * Calling {@link #setEnabled(boolean)} disables the component in the Swing
	 * hierarchy and invokes this method.
	 * </p>
	 *
	 * @param enabled
	 *            true if user input must be enabled, false otherwise
	 * @see #setEnabled(boolean)
	 * @see #isInputEnabled()
	 */
	public void setInputEnabled(boolean enabled) {
		if (inputEnabled != enabled) {
			boolean old = isInputEnabled();
			this.inputEnabled = enabled;

			if (enabled) {
				addMouseListener(resetHandler);
				addMouseListener(panHandler);
				addMouseMotionListener(panMotionHandler);
				addMouseWheelListener(zoomHandler);
			} else {
				removeMouseListener(resetHandler);
				removeMouseListener(panHandler);
				removeMouseMotionListener(panMotionHandler);
				removeMouseWheelListener(zoomHandler);
			}

			firePropertyChange("inputEnabled", old, isInputEnabled());
		}
	}

	/**
	 * <p>
	 * Defines whether or not user input is accepted and managed by this
	 * component. The component is always created with user input enabled.
	 * </p>
	 *
	 * @return true if user input is enabled, false otherwise
	 * @see #setInputEnabled(boolean)
	 */
	public boolean isInputEnabled() {
		return inputEnabled;
	}

	/**
	 * <p>
	 * Defines whether or not axis labels are painted by this component. The
	 * component is always created with text painting enabled.
	 * </p>
	 *
	 * @return true if axis labels are painted, false otherwise
	 * @see #setTextPainted(boolean)
	 * @see #getForeground()
	 */
	public boolean isTextPainted() {
		return textPainted;
	}

	/**
	 * <p>
	 * Enables or disables the painting of axis labels depending on the value of
	 * the parameter. Text painting is enabled by default.
	 * </p>
	 * 
	 * @param textPainted
	 *            if true, axis labels are painted
	 * @see #isTextPainted()
	 * @see #setForeground(Color)
	 */
	public void setTextPainted(boolean textPainted) {
		boolean old = isTextPainted();
		this.textPainted = textPainted;
		firePropertyChange("textPainted", old, this.textPainted);
	}

	/**
	 * <p>
	 * Defines whether or not grids lines are painted by this component. The
	 * component is always created with grid lines painting enabled.
	 * </p>
	 *
	 * @return true if grid lines are painted, false otherwise
	 * @see #setGridPainted(boolean)
	 * @see #getMajorGridColor()
	 * @see #getMinorGridColor()
	 */
	public boolean isGridPainted() {
		return gridPainted;
	}

	/**
	 * <p>
	 * Enables or disables the painting of grid lines depending on the value of
	 * the parameter. Grid painting is enabled by default.
	 * </p>
	 * 
	 * @param gridPainted
	 *            if true, axis labels are painted
	 * @see #isGridPainted()
	 * @see #setMajorGridColor(Color)
	 * @see #setMinorGridColor(Color)
	 */
	public void setGridPainted(boolean gridPainted) {
		boolean old = isGridPainted();
		this.gridPainted = gridPainted;
		firePropertyChange("gridPainted", old, isGridPainted());
	}

	/**
	 * <p>
	 * Defines whether or not the graph main axis is painted by this component.
	 * The component is always created with main axis painting enabled.
	 * </p>
	 *
	 * @return true if main axis is painted, false otherwise
	 * @see #setTextPainted(boolean)
	 * @see #getAxisColor()
	 */
	public boolean isAxisPainted() {
		return axisPainted;
	}

	/**
	 * <p>
	 * Enables or disables the painting of main axis depending on the value of
	 * the parameter. Axis painting is enabled by default.
	 * </p>
	 * 
	 * @param axisPainted
	 *            if true, axis labels are painted
	 * @see #isAxisPainted()
	 * @see #setAxisColor(Color)
	 */
	public void setAxisPainted(boolean axisPainted) {
		boolean old = isAxisPainted();
		this.axisPainted = axisPainted;
		firePropertyChange("axisPainted", old, isAxisPainted());
	}

	/**
	 * <p>
	 * Defines whether or not the background painted by this component. The
	 * component is always created with background painting enabled. When
	 * background painting is disabled, background painting is deferred to the
	 * parent class.
	 * </p>
	 *
	 * @return true if background is painted, false otherwise
	 * @see #setBackgroundPainted(boolean)
	 * @see #getBackground()
	 */
	public boolean isBackgroundPainted() {
		return backPainted;
	}

	/**
	 * <p>
	 * Enables or disables the painting of background depending on the value of
	 * the parameter. Background painting is enabled by default.
	 * </p>
	 * 
	 * @param backPainted
	 *            if true, axis labels are painted
	 * @see #isBackgroundPainted()
	 * @see #setBackground(Color)
	 */
	public void setBackgroundPainted(boolean backPainted) {
		boolean old = isBackgroundPainted();
		this.backPainted = backPainted;
		firePropertyChange("backgroundPainted", old, isBackgroundPainted());
	}

	/**
	 * <p>
	 * Gets the major grid lines color of this component.
	 * </p>
	 *
	 * @return this component's major grid lines color
	 * @see #setMajorGridColor(Color)
	 * @see #setGridPainted(boolean)
	 */
	public Color getMajorGridColor() {
		return majorGridColor;
	}

	/**
	 * <p>
	 * Sets the color of major grid lines on this component. The color can be
	 * translucent.
	 * </p>
	 *
	 * @param majorGridColor
	 *            the color to become this component's major grid lines color
	 * @throws IllegalArgumentException
	 *             if the specified color is null
	 * @see #getMajorGridColor()
	 * @see #isGridPainted()
	 */
	public void setMajorGridColor(Color majorGridColor) {
		if (majorGridColor == null) {
			throw new IllegalArgumentException("Color cannot be null.");
		}

		Color old = getMajorGridColor();
		this.majorGridColor = majorGridColor;
		firePropertyChange("majorGridColor", old, getMajorGridColor());
	}

	/**
	 * <p>
	 * Gets the minor grid lines color of this component.
	 * </p>
	 *
	 * @return this component's minor grid lines color
	 * @see #setMinorGridColor(Color)
	 * @see #setGridPainted(boolean)
	 */
	public Color getMinorGridColor() {
		return minorGridColor;
	}

	/**
	 * <p>
	 * Sets the color of minor grid lines on this component. The color can be
	 * translucent.
	 * </p>
	 *
	 * @param minorGridColor
	 *            the color to become this component's minor grid lines color
	 * @throws IllegalArgumentException
	 *             if the specified color is null
	 * @see #getMinorGridColor()
	 * @see #isGridPainted()
	 */
	public void setMinorGridColor(Color minorGridColor) {
		if (minorGridColor == null) {
			throw new IllegalArgumentException("Color cannot be null.");
		}

		Color old = getMinorGridColor();
		this.minorGridColor = minorGridColor;
		firePropertyChange("minorGridColor", old, getMinorGridColor());
	}

	/**
	 * <p>
	 * Gets the main axis color of this component.
	 * </p>
	 *
	 * @return this component's main axis color
	 * @see #setAxisColor(Color)
	 * @see #setGridPainted(boolean)
	 */
	public Color getAxisColor() {
		return axisColor;
	}

	/**
	 * <p>
	 * Sets the color of main axis on this component. The color can be
	 * translucent.
	 * </p>
	 *
	 * @param axisColor
	 *            the color to become this component's main axis color
	 * @throws IllegalArgumentException
	 *             if the specified color is null
	 * @see #getAxisColor()
	 * @see #isAxisPainted()
	 */
	public void setAxisColor(Color axisColor) {
		if (axisColor == null) {
			throw new IllegalArgumentException("Color cannot be null.");
		}

		Color old = getAxisColor();
		this.axisColor = axisColor;
		firePropertyChange("axisColor", old, getAxisColor());
	}

	/**
	 * <p>
	 * Gets the distance, in graph units, between two major grid lines on the X
	 * axis.
	 * </p>
	 *
	 * @return the spacing between two major grid lines on the X axis
	 * @see #setMajorX(double)
	 * @see #getMajorY()
	 * @see #setMajorY(double)
	 * @see #getMinorCountX()
	 * @see #setMinorCountX(int)
	 */
	public double getMajorX() {
		return majorX;
	}

	/**
	 * <p>
	 * Sets the distance, in graph units, between two major grid lines on the X
	 * axis.
	 * </p>
	 *
	 * @param majorX
	 *            the requested spacing between two major grid lines on the X
	 *            axis
	 * @throws IllegalArgumentException
	 *             if majorX is <= 0.0d
	 * @see #getMajorX()
	 * @see #getMajorY()
	 * @see #setMajorY(double)
	 * @see #getMinorCountX()
	 * @see #setMinorCountX(int)
	 */
	public void setMajorX(double majorX) {
		if (majorX <= 0.0) {
			throw new IllegalArgumentException("majorX must be > 0.0");
		}

		double old = getMajorX();
		this.majorX = majorX;
		this.defaultMajorX = majorX;
		repaint();
		firePropertyChange("majorX", old, getMajorX());
	}

	/**
	 * <p>
	 * Gets the number of minor grid lines between two major grid lines on the X
	 * axis.
	 * </p>
	 *
	 * @return the number of minor grid lines between two major grid lines
	 * @see #setMinorCountX(int)
	 * @see #getMinorCountY()
	 * @see #setMinorCountY(int)
	 * @see #getMajorX()
	 * @see #setMajorX(double)
	 */
	public int getMinorCountX() {
		return minorCountX;
	}

	/**
	 * <p>
	 * Sets the number of minor grid lines between two major grid lines on the X
	 * axis.
	 * </p>
	 *
	 * @param minorCountX
	 *            the number of minor grid lines between two major grid lines on
	 *            the X axis
	 * @throws IllegalArgumentException
	 *             if minorCountX is < 0
	 * @see #getMinorCountX()
	 * @see #getMinorCountY()
	 * @see #setMinorCountY(int)
	 * @see #getMajorX()
	 * @see #setMajorX(double)
	 */
	public void setMinorCountX(int minorCountX) {
		if (minorCountX < 0) {
			throw new IllegalArgumentException("minorCountX must be >= 0");
		}

		int old = getMinorCountX();
		this.minorCountX = minorCountX;
		repaint();
		firePropertyChange("minorCountX", old, getMinorCountX());
	}

	/**
	 * <p>
	 * Gets the distance, in graph units, between two major grid lines on the Y
	 * axis.
	 * </p>
	 *
	 * @return the spacing between two major grid lines on the Y axis
	 * @see #setMajorY(double)
	 * @see #getMajorX()
	 * @see #setMajorX(double)
	 * @see #getMinorCountY()
	 * @see #setMinorCountY(int)
	 */
	public double getMajorY() {
		return majorY;
	}

	/**
	 * <p>
	 * Sets the distance, in graph units, between two major grid lines on the Y
	 * axis.
	 * </p>
	 *
	 * @param majorY
	 *            the requested spacing between two major grid lines on the Y
	 *            axis
	 * @throws IllegalArgumentException
	 *             if majorY is <= 0.0d
	 * @see #getMajorY()
	 * @see #getMajorX()
	 * @see #setMajorX(double)
	 * @see #getMinorCountY()
	 * @see #setMinorCountY(int)
	 */
	public void setMajorY(double majorY) {
		if (majorY <= 0.0) {
			throw new IllegalArgumentException("majorY must be > 0.0");
		}

		double old = getMajorY();
		this.majorY = majorY;
		this.defaultMajorY = majorY;
		repaint();
		firePropertyChange("majorY", old, getMajorY());
	}

	/**
	 * <p>
	 * Gets the number of minor grid lines between two major grid lines on the Y
	 * axis.
	 * </p>
	 *
	 * @return the number of minor grid lines between two major grid lines
	 * @see #setMinorCountY(int)
	 * @see #getMinorCountX()
	 * @see #setMinorCountX(int)
	 * @see #getMajorY()
	 * @see #setMajorY(double)
	 */
	public int getMinorCountY() {
		return minorCountY;
	}

	/**
	 * <p>
	 * Sets the number of minor grid lines between two major grid lines on the Y
	 * axis.
	 * </p>
	 *
	 * @param minorCountY
	 *            the number of minor grid lines between two major grid lines on
	 *            the Y axis
	 * @throws IllegalArgumentException
	 *             if minorCountY is < 0
	 * @see #getMinorCountY()
	 * @see #getMinorCountX()
	 * @see #setMinorCountX(int)
	 * @see #getMajorY()
	 * @see #setMajorY(double)
	 */
	public void setMinorCountY(int minorCountY) {
		if (minorCountY < 0) {
			throw new IllegalArgumentException("minorCountY must be >= 0");
		}

		int old = getMinorCountY();
		this.minorCountY = minorCountY;
		repaint();
		firePropertyChange("minorCountY", old, getMinorCountY());
	}

	/**
	 * <p>
	 * Sets the view and the origin of the graph at the same time. The view
	 * minimum boundaries are defined by the location of the rectangle passed as
	 * parameter. The width and height of the rectangle define the distance
	 * between the minimum and maximum boundaries:
	 * </p>
	 *
	 * <ul>
	 * <li><i>minX</i>: bounds.getX()</li>
	 * <li><i>minY</i>: bounds.getY()</li>
	 * <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
	 * <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
	 * </ul>
	 *
	 * <p>
	 * The origin is located at the center of the view. Its coordinates are
	 * defined by calling bounds.getCenterX() and bounds.getCenterY().
	 * </p>
	 *
	 * @param bounds
	 *            the rectangle defining the graph's view and its origin
	 * @see #getView()
	 * @see #setView(Rectangle2D)
	 * @see #getOrigin()
	 * @see #setOrigin(Point2D)
	 */
	public void setViewAndOrigin(Rectangle2D bounds) {
		setView(bounds);
		setOrigin(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()));
	}

	/**
	 * <p>
	 * Sets the view of the graph. The view minimum boundaries are defined by
	 * the location of the rectangle passed as parameter. The width and height
	 * of the rectangle define the distance between the minimum and maximum
	 * boundaries:
	 * </p>
	 *
	 * <ul>
	 * <li><i>minX</i>: bounds.getX()</li>
	 * <li><i>minY</i>: bounds.getY()</li>
	 * <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
	 * <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
	 * </ul>
	 *
	 * <p>
	 * If the specified view is null, nothing happens.
	 * </p>
	 *
	 * <p>
	 * Calling this method leaves the origin intact.
	 * </p>
	 *
	 * @param bounds
	 *            the rectangle defining the graph's view and its origin
	 * @see #getView()
	 * @see #setViewAndOrigin(Rectangle2D)
	 */
	public void setView(Rectangle2D bounds) {
		if (bounds == null) {
			return;
		}
		Rectangle2D old = getView();
		defaultView = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());

		minX = defaultView.getMinX();
		maxX = defaultView.getMaxX();
		minY = defaultView.getMinY();
		maxY = defaultView.getMaxY();

		majorX = defaultMajorX;
		majorY = defaultMajorY;
		firePropertyChange("view", old, getView());
		repaint();
	}

	/**
	 * <p>
	 * Gets the view of the graph. The returned rectangle defines the bounds of
	 * the view as follows:
	 * </p>
	 *
	 * <ul>
	 * <li><i>minX</i>: bounds.getX()</li>
	 * <li><i>minY</i>: bounds.getY()</li>
	 * <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
	 * <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
	 * </ul>
	 *
	 * @return the rectangle corresponding to the current view of the graph
	 * @see #setView(Rectangle2D)
	 * @see #setViewAndOrigin(Rectangle2D)
	 */
	public Rectangle2D getView() {
		return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
	}

	/**
	 * <p>
	 * Resets the view to the default view if it has been changed by the user by
	 * panning and zooming. The default view is defined by the view last
	 * specified in a constructor call or a call to the methods
	 * {@link #setView(Rectangle2D)} and {@link #setViewAndOrigin(Rectangle2D)}.
	 * </p>
	 *
	 * @see #setView(Rectangle2D)
	 * @see #setViewAndOrigin(Rectangle2D)
	 */
	public void resetView() {
		setView(defaultView);
	}

	/**
	 * <p>
	 * Sets the origin of the graph. The coordinates of the origin are defined
	 * by the coordinates of the point passed as parameter.
	 * </p>
	 *
	 * <p>
	 * If the specified view is null, nothing happens.
	 * </p>
	 *
	 * <p>
	 * Calling this method leaves the view intact.
	 * </p>
	 *
	 * @param origin
	 *            the coordinates of the new origin
	 * @see #getOrigin()
	 * @see #setViewAndOrigin(Rectangle2D)
	 */
	public void setOrigin(Point2D origin) {
		if (origin == null) {
			return;
		}

		Point2D old = getOrigin();
		originX = origin.getX();
		originY = origin.getY();
		firePropertyChange("origin", old, getOrigin());
		repaint();
	}

	/**
	 * <p>
	 * Gets the origin coordinates of the graph. The coordinates are represented
	 * as an instance of <code>Point2D</code> and stored in <code>double</code>
	 * format.
	 * </p>
	 * 
	 * @return the origin coordinates in double format
	 * @see #setOrigin(Point2D)
	 * @see #setViewAndOrigin(Rectangle2D)
	 */
	public Point2D getOrigin() {
		return new Point2D.Double(originX, originY);
	}

	/**
	 * <p>
	 * Adds one or more plots to the graph. These plots are associated to a
	 * color used to draw them.
	 * </p>
	 * 
	 * <p>
	 * If plotList is null or empty, nothing happens.
	 * </p>
	 *
	 * <p>
	 * This method is not thread safe and should be called only from the EDT.
	 * </p>
	 *
	 * @param color
	 *            the color to be usd to draw the plots
	 * @param plotList
	 *            the list of plots to add to the graph
	 * @throws IllegalArgumentException
	 *             if color is null
	 * @see #removePlots(JXGraph.Plot...)
	 * @see #removeAllPlots()
	 */
	public void addPlots(Color color, Plot... plotList) {
		if (color == null) {
			throw new IllegalArgumentException("Plots color cannot be null.");
		}

		if (plotList == null) {
			return;
		}

		for (Plot plot : plotList) {
			DrawablePlot drawablePlot = new DrawablePlot(plot, color);
			if (plot != null && !plots.contains(drawablePlot)) {
				plot.addPropertyChangeListener(plotChangeListener);
				plots.add(drawablePlot);
			}
		}
		repaint();
	}

	/**
	 * <p>
	 * Removes the specified plots from the graph. Plots to be removed are
	 * identified by identity. This means you cannot remove a plot by passing a
	 * clone or another instance of the same subclass of {@link JXGraph.Plot}.
	 * </p>
	 *
	 * <p>
	 * If plotList is null or empty, nothing happens.
	 * </p>
	 *
	 * <p>
	 * This method is not thread safe and should be called only from the EDT.
	 * </p>
	 *
	 * @param plotList
	 *            the list of plots to be removed from the graph
	 * @see #removeAllPlots()
	 * @see #addPlots(Color, JXGraph.Plot...)
	 */
	public void removePlots(Plot... plotList) {
		if (plotList == null) {
			return;
		}

		for (Plot plot : plotList) {
			if (plot != null) {
				DrawablePlot toRemove = null;
				for (DrawablePlot drawable : plots) {
					if (drawable.getEquation() == plot) {
						toRemove = drawable;
						break;
					}
				}

				if (toRemove != null) {
					plot.removePropertyChangeListener(plotChangeListener);
					plots.remove(toRemove);
				}
			}
		}
		repaint();
	}

	/**
	 * <p>
	 * Removes all the plots currently associated with this graph.
	 * </p>
	 *
	 * <p>
	 * This method is not thread safe and should be called only from the EDT.
	 * </p>
	 *
	 * @see #removePlots(JXGraph.Plot...)
	 * @see #addPlots(Color, JXGraph.Plot...)
	 */
	public void removeAllPlots() {
		plots.clear();
		repaint();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(400, 400);
	}

	/**
	 * <p>
	 * Converts a position, in graph units, from the Y axis into a pixel
	 * coordinate. For instance, if you defined the origin so it appears at the
	 * exact center of the view, calling
	 * <code>yPositionToPixel(getOriginY())</code> will return a value
	 * approximately equal to <code>getHeight() / 2.0</code>.
	 * </p>
	 *
	 * @param position
	 *            the Y position to be converted into pixels
	 * @return the coordinate in pixels of the specified graph Y position
	 * @see #xPositionToPixel(double)
	 * @see #yPixelToPosition(double)
	 */
	protected double yPositionToPixel(double position) {
		double height = getHeight();
		return height - ((position - minY) * height / (maxY - minY));
	}

	/**
	 * <p>
	 * Converts a position, in graph units, from the X axis into a pixel
	 * coordinate. For instance, if you defined the origin so it appears at the
	 * exact center of the view, calling
	 * <code>xPositionToPixel(getOriginX())</code> will return a value
	 * approximately equal to <code>getWidth() / 2.0</code>.
	 * </p>
	 *
	 * @param position
	 *            the X position to be converted into pixels
	 * @return the coordinate in pixels of the specified graph X position
	 * @see #yPositionToPixel(double)
	 * @see #xPixelToPosition(double)
	 */
	protected double xPositionToPixel(double position) {
		return (position - minX) * getWidth() / (maxX - minX);
	}

	/**
	 * <p>
	 * Converts a pixel coordinate from the X axis into a graph position, in
	 * graph units. For instance, if you defined the origin so it appears at the
	 * exact center of the view, calling
	 * <code>xPixelToPosition(getWidth() / 2.0)</code> will return a value
	 * approximately equal to <code>getOriginX()</code>.
	 * </p>
	 *
	 * @param pixel
	 *            the X pixel coordinate to be converted into a graph position
	 * @return the graph X position of the specified pixel coordinate
	 * @see #yPixelToPosition(double)
	 * @see #xPositionToPixel(double)
	 */
	protected double xPixelToPosition(double pixel) {
		// double axisV = xPositionToPixel(originX);
		// return (pixel - axisV) * (maxX - minX) / (double) getWidth();
		return minX + pixel * (maxX - minX) / getWidth();
	}

	/**
	 * <p>
	 * Converts a pixel coordinate from the Y axis into a graph position, in
	 * graph units. For instance, if you defined the origin so it appears at the
	 * exact center of the view, calling
	 * <code>yPixelToPosition(getHeight() / 2.0)</code> will return a value
	 * approximately equal to <code>getOriginY()</code>.
	 * </p>
	 *
	 * @param pixel
	 *            the Y pixel coordinate to be converted into a graph position
	 * @return the graph Y position of the specified pixel coordinate
	 * @see #xPixelToPosition(double)
	 * @see #yPositionToPixel(double)
	 */
	protected double yPixelToPosition(double pixel) {
		// double axisH = yPositionToPixel(originY);
		// return (getHeight() - pixel - axisH) * (maxY - minY) / (double)
		// getHeight();
		return minY + (getHeight() - pixel) * (maxY - minY) / getHeight();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void paintComponent(Graphics g) {
		if (!isVisible()) {
			return;
		}

		Graphics2D g2 = (Graphics2D) g;
		setupGraphics(g2);

		paintBackground(g2);
		drawGrid(g2);
		drawAxis(g2);
		drawPlots(g2);
		drawLabels(g2);

		paintExtra(g2);
	}

	/**
	 * <p>
	 * This painting method is meant to be overridden by subclasses of
	 * <code>JXGraph</code>. This method is called after all the painting is
	 * done. By overriding this method, a subclass can display extra information
	 * on top of the graph.
	 * </p>
	 * <p>
	 * The graphics surface passed as parameter is configured by
	 * {@link #setupGraphics(Graphics2D)}.
	 * </p>
	 *
	 * @param g2
	 *            the graphics surface on which the graph is drawn
	 * @see #setupGraphics(Graphics2D)
	 * @see #xPixelToPosition(double)
	 * @see #yPixelToPosition(double)
	 * @see #xPositionToPixel(double)
	 * @see #yPositionToPixel(double)
	 */
	protected void paintExtra(Graphics2D g2) {
	}

	// Draw all the registered plots with the appropriate color.
	private void drawPlots(Graphics2D g2) {
		for (DrawablePlot drawable : plots) {
			g2.setColor(drawable.getColor());
			drawPlot(g2, drawable.getEquation());
		}
	}

	// Draw a single plot as a GeneralPath made of straight lines.
	private void drawPlot(Graphics2D g2, Plot equation) {
		float x = 0.0f;
		float y = (float) yPositionToPixel(equation.compute(xPixelToPosition(0.0)));

		GeneralPath path = new GeneralPath();
		path.moveTo(x, y);

		float width = getWidth();
		for (x = 0.0f; x < width; x += 1.0f) {
			double position = xPixelToPosition(x);
			y = (float) yPositionToPixel(equation.compute(position));
			path.lineTo(x, y);
		}

		g2.draw(path);
	}

	// Draws the grid. First draw the vertical lines, then the horizontal lines.
	private void drawGrid(Graphics2D g2) {
		Stroke stroke = g2.getStroke();

		if (isGridPainted()) {
			drawVerticalGrid(g2);
			drawHorizontalGrid(g2);
		}

		g2.setStroke(stroke);
	}

	// Draw all labels. First draws labels on the horizontal axis, then labels
	// on the vertical axis. If the axis is set not to be painted, this
	// method draws the origin as a straight cross.
	private void drawLabels(Graphics2D g2) {
		if (isTextPainted()) {
			double axisH = yPositionToPixel(originY);
			double axisV = xPositionToPixel(originX);

			if (isAxisPainted()) {
				Stroke stroke = g2.getStroke();
				g2.setStroke(new BasicStroke(STROKE_AXIS));
				g2.setColor(getAxisColor());
				g2.drawLine((int) axisV - 3, (int) axisH, (int) axisV + 3, (int) axisH);
				g2.drawLine((int) axisV, (int) axisH - 3, (int) axisV, (int) axisH + 3);
				g2.setStroke(stroke);
			}

			g2.setColor(getForeground());
			FontMetrics metrics = g2.getFontMetrics();
			g2.drawString(format(originX) + "; " + format(originY), (int) axisV + 5, (int) axisH + metrics.getHeight());

			drawHorizontalAxisLabels(g2);
			drawVerticalAxisLabels(g2);
		}
	}

	// Draws labels on the vertical axis. First draws labels below the origin,
	// then draw labels on top of the origin.
	private void drawVerticalAxisLabels(Graphics2D g2) {
		double axisV = xPositionToPixel(originX);

		// double startY = Math.floor((minY - originY) / majorY) * majorY;
		double startY = Math.floor(minY / majorY) * majorY;
		for (double y = startY; y < maxY + majorY; y += majorY) {
			if (((y - majorY / 2.0) < originY) && ((y + majorY / 2.0) > originY)) {
				continue;
			}

			int position = (int) yPositionToPixel(y);
			g2.drawString(format(y), (int) axisV + 5, position);
		}
	}

	// Draws the horizontal lines of the grid. Draws both minor and major
	// grid lines.
	private void drawHorizontalGrid(Graphics2D g2) {
		double minorSpacing = majorY / getMinorCountY();
		double axisV = xPositionToPixel(originX);

		Stroke gridStroke = new BasicStroke(STROKE_GRID);
		Stroke axisStroke = new BasicStroke(STROKE_AXIS);

		Rectangle clip = g2.getClipBounds();

		int position;

		if (!isAxisPainted()) {
			position = (int) xPositionToPixel(originX);
			if (position >= clip.x && position <= clip.x + clip.width) {
				g2.setColor(getMajorGridColor());
				g2.drawLine(position, clip.y, position, clip.y + clip.height);
			}
		}

		// double startY = Math.floor((minY - originY) / majorY) * majorY;
		double startY = Math.floor(minY / majorY) * majorY;
		for (double y = startY; y < maxY + majorY; y += majorY) {
			g2.setStroke(gridStroke);
			g2.setColor(getMinorGridColor());
			for (int i = 0; i < getMinorCountY(); i++) {
				position = (int) yPositionToPixel(y - i * minorSpacing);
				if (position >= clip.y && position <= clip.y + clip.height) {
					g2.drawLine(clip.x, position, clip.x + clip.width, position);
				}
			}

			position = (int) yPositionToPixel(y);
			if (position >= clip.y && position <= clip.y + clip.height) {
				g2.setColor(getMajorGridColor());
				g2.drawLine(clip.x, position, clip.x + clip.width, position);

				if (isAxisPainted()) {
					g2.setStroke(axisStroke);
					g2.setColor(getAxisColor());
					g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
				}
			}
		}
	}

	// Draws labels on the horizontal axis. First draws labels on the right of
	// the origin, then on the left.
	private void drawHorizontalAxisLabels(Graphics2D g2) {
		double axisH = yPositionToPixel(originY);
		FontMetrics metrics = g2.getFontMetrics();

		// double startX = Math.floor((minX - originX) / majorX) * majorX;
		double startX = Math.floor(minX / majorX) * majorX;
		for (double x = startX; x < maxX + majorX; x += majorX) {
			if (((x - majorX / 2.0) < originX) && ((x + majorX / 2.0) > originX)) {
				continue;
			}

			int position = (int) xPositionToPixel(x);
			g2.drawString(format(x), position, (int) axisH + metrics.getHeight());
		}
	}

	// Draws the vertical lines of the grid. Draws both minor and major
	// grid lines.
	private void drawVerticalGrid(Graphics2D g2) {
		double minorSpacing = majorX / getMinorCountX();
		double axisH = yPositionToPixel(originY);

		Stroke gridStroke = new BasicStroke(STROKE_GRID);
		Stroke axisStroke = new BasicStroke(STROKE_AXIS);

		Rectangle clip = g2.getClipBounds();

		int position;
		if (!isAxisPainted()) {
			position = (int) yPositionToPixel(originY);
			if (position >= clip.y && position <= clip.y + clip.height) {
				g2.setColor(getMajorGridColor());
				g2.drawLine(clip.x, position, clip.x + clip.width, position);
			}
		}

		// double startX = Math.floor((minX - originX) / majorX) * majorX;
		double startX = Math.floor(minX / majorX) * majorX;
		for (double x = startX; x < maxX + majorX; x += majorX) {
			g2.setStroke(gridStroke);
			g2.setColor(getMinorGridColor());
			for (int i = 0; i < getMinorCountX(); i++) {
				position = (int) xPositionToPixel(x - i * minorSpacing);
				if (position >= clip.x && position <= clip.x + clip.width) {
					g2.drawLine(position, clip.y, position, clip.y + clip.height);
				}
			}

			position = (int) xPositionToPixel(x);
			if (position >= clip.x && position <= clip.x + clip.width) {
				g2.setColor(getMajorGridColor());
				g2.drawLine(position, clip.y, position, clip.y + clip.height);

				if (isAxisPainted()) {
					g2.setStroke(axisStroke);
					g2.setColor(getAxisColor());
					g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
				}
			}
		}
	}

	// Drase the main axis.
	private void drawAxis(Graphics2D g2) {
		if (!isAxisPainted()) {
			return;
		}

		double axisH = yPositionToPixel(originY);
		double axisV = xPositionToPixel(originX);

		Rectangle clip = g2.getClipBounds();

		g2.setColor(getAxisColor());
		Stroke stroke = g2.getStroke();
		g2.setStroke(new BasicStroke(STROKE_AXIS));

		if (axisH >= clip.y && axisH <= clip.y + clip.height) {
			g2.drawLine(clip.x, (int) axisH, clip.x + clip.width, (int) axisH);
		}
		if (axisV >= clip.x && axisV <= clip.x + clip.width) {
			g2.drawLine((int) axisV, clip.y, (int) axisV, clip.y + clip.height);
		}

		g2.setStroke(stroke);
	}

	/**
	 * <p>
	 * This method is called by the component prior to any drawing operation to
	 * configure the drawing surface. The default implementation enables
	 * antialiasing on the graphics.
	 * </p>
	 * <p>
	 * This method can be overriden by subclasses to modify the drawing surface
	 * before any painting happens.
	 * </p>
	 *
	 * @param g2
	 *            the graphics surface to set up
	 * @see #paintExtra(Graphics2D)
	 * @see #paintBackground(Graphics2D)
	 */
	protected void setupGraphics(Graphics2D g2) {
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	}

	/**
	 * <p>
	 * This method is called by the component whenever it needs to paint its
	 * background. The default implementation fills the background with a solid
	 * color as defined by {@link #getBackground()}. Background painting does
	 * not happen when {@link #isBackgroundPainted()} returns false.
	 * </p>
	 * <p>
	 * It is recommended to subclasses to honor the contract defined by
	 * {@link #isBackgroundPainted()} and {@link #setBackgroundPainted(boolean)}.
	 *
	 * @param g2
	 *            the graphics surface on which the background must be drawn
	 * @see #setupGraphics(Graphics2D)
	 * @see #paintExtra(Graphics2D)
	 * @see #isBackgroundPainted()
	 * @see #setBackgroundPainted(boolean)
	 */
	protected void paintBackground(Graphics2D g2) {
		if (isBackgroundPainted()) {
			Painter p = getBackgroundPainter();
			if (p != null) {
				p.paint(g2, this, getWidth(), getHeight());
			} else {
				g2.setColor(getBackground());
				g2.fill(g2.getClipBounds());
			}
		}
	}

	// Format a number with the appropriate number formatter. Numbers >= 0.01
	// and < 100 are formatted with a regular, 2-digits, numbers formatter.
	// Other numbers use a scientific notation given by a DecimalFormat instance
	private String format(double number) {
		boolean farAway = (number != 0.0d && Math.abs(number) < 0.01d) || Math.abs(number) > 99.0d;
		return (farAway ? secondFormatter : mainFormatter).format(number);
	}

	/**
	 * <p>
	 * A plot represents a mathematical transformation used by {@link JXGraph}.
	 * When a plot belongs to a graph, the graph component asks for the
	 * transformation of a value along the X axis. The resulting value defines
	 * the Y coordinates at which the graph must draw a spot of color.
	 * </p>
	 *
	 * <p>
	 * Here is a sample implementation of this class that draws a straight line
	 * once added to a graph (it follows the well-known equation y=a.x+b):
	 * </p>
	 *
	 * <pre>
	 * class LinePlot extends JXGraph.Plot {
	 * 	public double compute(double value) {
	 * 		return 2.0 * value + 1.0;
	 * 	}
	 * }
	 * </pre>
	 *
	 * <p>
	 * When a plot is added to an instance of <code>JXGraph</code>, the
	 * <code>JXGraph</code> automatically becomes a new property change listener
	 * of the plot. If property change events are fired, the graph will be
	 * updated accordingly.
	 * </p>
	 * 
	 * <p>
	 * More information about plots usage can be found in {@link JXGraph} in the
	 * section entitled <i>Plots</i>.
	 * </p>
	 *
	 * @see JXGraph
	 * @see JXGraph#addPlots(Color, JXGraph.Plot...)
	 */
	public abstract static class Plot extends AbstractBean {
		/**
		 * <p>
		 * Creates a new, parameter-less plot.
		 * </p>
		 */
		protected Plot() {
		}

		/**
		 * <p>
		 * This method must return the result of a mathematical transformation
		 * of its sole parameter.
		 * </p>
		 * 
		 * @param value
		 *            a value along the X axis of the graph currently drawing
		 *            this plot
		 * @return the result of the mathematical transformation of value
		 */
		public abstract double compute(double value);
	}

	// Encapsulates a plot and its color. Avoids the use of a full-blown Map.
	private static class DrawablePlot {
		private final Plot equation;
		private final Color color;

		private DrawablePlot(Plot equation, Color color) {
			this.equation = equation;
			this.color = color;
		}

		private Plot getEquation() {
			return equation;
		}

		private Color getColor() {
			return color;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}
			final DrawablePlot that = (DrawablePlot) o;
			if (!color.equals(that.color)) {
				return false;
			}
			return equation.equals(that.equation);
		}

		@Override
		public int hashCode() {
			int result;
			result = equation.hashCode();
			result = 29 * result + color.hashCode();
			return result;
		}
	}

	// Shrinks or expand the view depending on the mouse wheel direction.
	// When the wheel moves down, the view is expanded. Otherwise it is shrunk.
	private class ZoomHandler implements MouseWheelListener {
		@Override
		public void mouseWheelMoved(MouseWheelEvent e) {
			double distanceX = maxX - minX;
			double distanceY = maxY - minY;

			double cursorX = minX + distanceX / 2.0;
			double cursorY = minY + distanceY / 2.0;

			int rotation = e.getWheelRotation();
			if (rotation < 0) {
				distanceX /= ZOOM_MULTIPLIER;
				distanceY /= ZOOM_MULTIPLIER;

				majorX /= ZOOM_MULTIPLIER;
				majorY /= ZOOM_MULTIPLIER;
			} else {
				distanceX *= ZOOM_MULTIPLIER;
				distanceY *= ZOOM_MULTIPLIER;

				majorX *= ZOOM_MULTIPLIER;
				majorY *= ZOOM_MULTIPLIER;
			}

			minX = cursorX - distanceX / 2.0;
			maxX = cursorX + distanceX / 2.0;
			minY = cursorY - distanceY / 2.0;
			maxY = cursorY + distanceY / 2.0;

			repaint();
		}
	}

	// Listens for a click on the middle button of the mouse and resets the view
	private class ResetHandler extends MouseAdapter {
		@Override
		public void mousePressed(MouseEvent e) {
			if (e.getButton() != MouseEvent.BUTTON2) {
				return;
			}

			resetView();
		}
	}

	// Starts and ends drag gestures with mouse left button.
	private class PanHandler extends MouseAdapter {
		@Override
		public void mousePressed(MouseEvent e) {
			if (e.getButton() != MouseEvent.BUTTON1) {
				return;
			}

			dragStart = e.getPoint();
			setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (e.getButton() != MouseEvent.BUTTON1) {
				return;
			}

			setCursor(Cursor.getDefaultCursor());
		}
	}

	// Handles drag gesture with the left mouse button and relocates the view
	// accordingly.
	private class PanMotionHandler extends MouseMotionAdapter {
		@Override
		public void mouseDragged(MouseEvent e) {
			Point dragEnd = e.getPoint();

			double distance = xPixelToPosition(dragEnd.getX()) - xPixelToPosition(dragStart.getX());
			minX = minX - distance;
			maxX = maxX - distance;

			distance = yPixelToPosition(dragEnd.getY()) - yPixelToPosition(dragStart.getY());
			minY = minY - distance;
			maxY = maxY - distance;

			repaint();
			dragStart = dragEnd;
		}
	}
}
